package pl.polsl.pum2.pj.rubik;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;

import javax.microedition.khronos.opengles.GL10;

import pl.polsl.pum2.pj.math.Quaternion3d;
import pl.polsl.pum2.pj.math.Vector3d;

import android.opengl.Matrix;

public class Cube {

  private FloatBuffer         vertexBuffer;
  private FloatBuffer         colorBuffer;

  //Reference Vector (use it for all rotations for locationVec)
  private Vector3d            transVec          = Vector3d.empty();

  //Vector contains Cube updated placement in space (Rubik cube)
  private Vector3d            locationVec;
  public Quaternion3d         quaternion        = Quaternion3d.identity();
  public float[]              rotMatrix         = new float[16];

  private static final float  SIZE              = 0.5f;

  private int                 id                = 0;
  private float               alpha             = 0;

  private ArrayList<float[]>  oryginalVecColors = new ArrayList<float[]>();
  private ArrayList<Vector3d> actualVecColors   = new ArrayList<Vector3d>();

  public Cube(Vector3d transformVec, int id, int x, int y, int z, int size) {
    setVertices();
    transVec = transformVec;
    locationVec = transformVec.clone();
    Matrix.setIdentityM(rotMatrix, 0);

    this.id = id;
    alpha = (float) id / 255f;

    ArrayList<float[]> colors = new ArrayList<float[]>();
    for (int i = 0; i < 24; ++i) {
      colors.add(new float[] {0.2f, 0.2f, 0.2f, alpha});
    }

    int max = size - 1;
    if (x == 0) {
      oryginalVecColors.add(new float[] {-1 * ColorLength.GREEN, 0, 0, 0});
      for (int i = 16; i < 20; ++i) {
        colors.set(i, new float[] {0, 1, 0, alpha});
      }
    }
    if (y == 0) {
      oryginalVecColors.add(new float[] {0, -1 * ColorLength.YELLOW, 0, 0});
      for (int i = 4; i < 8; ++i) {
        colors.set(i, new float[] {1, 1, 0, alpha});
      }
    }
    if (z == 0) {
      oryginalVecColors.add(new float[] {0, 0, -1 * ColorLength.ORANGE, 0});
      for (int i = 12; i < 16; ++i) {
        colors.set(i, new float[] {1, 0.5f, 0, alpha});
      }
    }
    if (x == max) {
      oryginalVecColors.add(new float[] {1 * ColorLength.BLUE, 0, 0, 0});
      for (int i = 20; i < 24; ++i) {
        colors.set(i, new float[] {0, 0, 1, alpha});
      }
    }
    if (y == max) {
      oryginalVecColors.add(new float[] {0, 1 * ColorLength.WHITE, 0, 0});
      for (int i = 0; i < 4; ++i) {
        colors.set(i, new float[] {1, 1, 1, alpha});
      }
    }
    if (z == max) {
      oryginalVecColors.add(new float[] {0, 0, 1 * ColorLength.RED, 0});
      for (int i = 8; i < 12; ++i) {
        colors.set(i, new float[] {1, 0, 0, alpha});
      }
    }

    ByteBuffer cbb = ByteBuffer.allocateDirect(colors.size() * 16);
    cbb.order(ByteOrder.nativeOrder());
    colorBuffer = cbb.asFloatBuffer();
    for (float[] color : colors) {
      colorBuffer.put(color);
    }
    colorBuffer.position(0);

    for (int i = 0; i < oryginalVecColors.size(); ++i) {
      actualVecColors.add(new Vector3d(oryginalVecColors.get(i)[0], oryginalVecColors.get(i)[1], oryginalVecColors.get(i)[2]));
    }
  }

  public ArrayList<Vector3d> getVecColors() {
    return actualVecColors;
  }

  public Vector3d getReferenceVec() {
    return transVec;
  }

  public Vector3d getLocationVector() {
    return locationVec;
  }

  private void setVertices() {
    Vector3d vertices[] = new Vector3d[24];

    //Top
    vertices[0] = new Vector3d(-SIZE, SIZE, SIZE);
    vertices[1] = new Vector3d(SIZE, SIZE, SIZE);
    vertices[2] = new Vector3d(-SIZE, SIZE, -SIZE);
    vertices[3] = new Vector3d(SIZE, SIZE, -SIZE);
    //Bottom
    vertices[4] = new Vector3d(SIZE, -SIZE, -SIZE);
    vertices[5] = new Vector3d(SIZE, -SIZE, SIZE);
    vertices[6] = new Vector3d(-SIZE, -SIZE, -SIZE);
    vertices[7] = new Vector3d(-SIZE, -SIZE, SIZE);
    //Front
    vertices[8] = new Vector3d(-SIZE, -SIZE, SIZE);
    vertices[9] = new Vector3d(SIZE, -SIZE, SIZE);
    vertices[10] = new Vector3d(-SIZE, SIZE, SIZE);
    vertices[11] = new Vector3d(SIZE, SIZE, SIZE);
    //Back
    vertices[12] = new Vector3d(SIZE, -SIZE, -SIZE);
    vertices[13] = new Vector3d(-SIZE, -SIZE, -SIZE);
    vertices[14] = new Vector3d(SIZE, SIZE, -SIZE);
    vertices[15] = new Vector3d(-SIZE, SIZE, -SIZE);
    //Left
    vertices[16] = new Vector3d(-SIZE, -SIZE, -SIZE);
    vertices[17] = new Vector3d(-SIZE, -SIZE, SIZE);
    vertices[18] = new Vector3d(-SIZE, SIZE, -SIZE);
    vertices[19] = new Vector3d(-SIZE, SIZE, SIZE);
    //Right
    vertices[20] = new Vector3d(SIZE, -SIZE, SIZE);
    vertices[21] = new Vector3d(SIZE, -SIZE, -SIZE);
    vertices[22] = new Vector3d(SIZE, SIZE, SIZE);
    vertices[23] = new Vector3d(SIZE, SIZE, -SIZE);

    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 3 * 4);
    byteBuffer.order(ByteOrder.nativeOrder());
    vertexBuffer = byteBuffer.asFloatBuffer();
    for (Vector3d vertex : vertices) {
      vertexBuffer.put(vertex.toFloatArray3());
    }
    vertexBuffer.position(0);
  }

  /**
   * Rotates the cube with the provided quaternion (multiplies current quat with given quat, saves resultant quat).
   */
  public void rotate(Quaternion3d quatRotation) {
    quaternion = Quaternion3d.multiply(quaternion, quatRotation);
    Quaternion3d.setMatrixFromQuaternion(rotMatrix, quaternion);

    float[] tab = new float[4];
    Matrix.multiplyMV(tab, 0, rotMatrix, 0, transVec.toFloatArray4(), 0);
    locationVec.updateFromArray(tab);

    float[] vectorTab = new float[4];
    for (int i = 0; i < oryginalVecColors.size(); ++i) {
      Matrix.multiplyMV(vectorTab, 0, rotMatrix, 0, oryginalVecColors.get(i), 0);
      actualVecColors.get(i).updateFromArray(vectorTab);
    }
  }

  /**
   * Sets the rotMatrix & locationVec using a given quaternion, but does not affect the original cube's quat.<br>
   * Invoking this method does not have persistent effects. It doesn't change the key values.
   * 
   * @param quatRotation
   */
  public void tempRotation(Quaternion3d quatRotation) {
    Quaternion3d.setMatrixFromQuaternion(rotMatrix, quatRotation);

    float[] tab = new float[4];
    Matrix.multiplyMV(tab, 0, rotMatrix, 0, transVec.toFloatArray4(), 0);
    locationVec.updateFromArray(tab);
  }

  public void draw(GL10 gl) {
    // Counter-clockwise winding.	
    gl.glFrontFace(GL10.GL_CW); // OpenGL docs
    // Enable face culling.
    gl.glEnable(GL10.GL_CULL_FACE); // OpenGL docs
    // What faces to remove with the face culling.
    gl.glCullFace(GL10.GL_FRONT); // OpenGL docs
    //gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
    gl.glPushMatrix();
    gl.glMultMatrixf(rotMatrix, 0);
    gl.glTranslatef(transVec.x, transVec.y, transVec.z);

    gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

    gl.glColorPointer(4, GL10.GL_FLOAT, 0, colorBuffer);
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);

    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, 4);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 4, 4);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 8, 4);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 12, 4);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 16, 4);
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 20, 4);

    gl.glPopMatrix();
    gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glDisableClientState(GL10.GL_COLOR_ARRAY);

    gl.glDisable(GL10.GL_CULL_FACE);
  }

  public int getId() {
    return id;
  }

  public static float[] colorToInitialDirection4(ColorAndId color) {
    int intValue = color.getColor();
    if (intValue == ColorAndId.WHITE) {
      float[] floats = {0, 1, 0, 0};
      return floats;
    } else if (intValue == ColorAndId.YELLOW) {
      float[] floats = {0, -1, 0, 0};
      return floats;
    } else if (intValue == ColorAndId.RED) {
      float[] floats = {0, 0, 1, 0};
      return floats;
    } else if (intValue == ColorAndId.ORANGE) {
      float[] floats = {0, 0, -1, 0};
      return floats;
    } else if (intValue == ColorAndId.GREEN) {
      float[] floats = {-1, 0, 0, 0};
      return floats;
    } else if (intValue == ColorAndId.BLUE) {
      float[] floats = {1, 0, 0, 0};
      return floats;
    } else {
      return null;
    }
  }

  private class ColorLength {
    public static final float WHITE  = 1f;
    public static final float YELLOW = 2f;
    public static final float RED    = 3f;
    public static final float ORANGE = 4f;
    public static final float GREEN  = 5f;
    public static final float BLUE   = 6f;
  }
}
